<?php
declare(strict_types=1);

namespace App\Controller;

use App\Constants\CacheKey;
use App\Constants\Consts;
use App\Middleware\GuzzleRetryMiddleware;
use Hyperf\Context\ApplicationContext;
use Hyperf\Contract\OnCloseInterface;
use Hyperf\Contract\OnMessageInterface;
use Hyperf\Contract\OnOpenInterface;
use Hyperf\Engine\WebSocket\Frame;
use Hyperf\Engine\WebSocket\Response;
use Hyperf\WebSocketServer\Constant\Opcode;
use GuzzleHttp\Handler\CurlHandler;
use Swoole\WebSocket\Server;
use function Hyperf\Support\env;

/**
 * 域名上传包生成及下载
 */
class WebSocketController implements OnMessageInterface, OnOpenInterface, OnCloseInterface
{
    /**
     * 检查连接是否活跃
     */
    private function isConnectionActive(Server $server, int $fd): bool
    {
        try {
            // 简单的连接存在检查
            if ($server->isEstablished($fd) === false) {
                return false;
            }

            // 检查连接是否存在且是 WebSocket 连接
            $connectionInfo = $server->getClientInfo($fd);
            if ($connectionInfo === false) {
                return false;
            }

            // 检查连接是否关闭或错误状态
            if (isset($info['close_errno']) && $info['close_errno'] !== 0) {
                return false;
            }

            // 对于 WebSocket 连接，检查状态
            if (isset($info['websocket_status'])) {
                return $info['websocket_status'] === WEBSOCKET_STATUS_ACTIVE;
            }

            return true;
        } catch (\Throwable $e) {
            return false;
        }
    }

    /**
     * 根据字符串判断结果是否为ok的组号
     */
    protected function _okfun(string $str)
    {
        preg_match('/ok\|[a-z]\d+\_\d{1,2}/i', strtolower($str), $mat);
        return $mat[0] ?? mb_substr($str, 0, 13);
    }

    /**
     * 根据网页内容判断body
     */
    protected function getBodyByHtml(string $htmlBody, int $maxlen, array $pythonBodys, bool $tiao = false): array
    {
        $ret = Consts::RET_FAILURE;
        $body = '';
        // 网页语言
        $lang = '';
        preg_match('/html[\s\S]lang="(.*?)"/i', $htmlBody, $matches);
        if (!empty($matches[1] ?? null)) {
            $lang = strtolower($matches[1]);
        }
        if (str_contains($htmlBody, 'verifying that you are not a robot')) {
            $body = $pythonBodys[0];
        } elseif (str_contains($htmlBody, 'bandwidth limit exceeded')) {
            $body = $pythonBodys[2];
        } elseif (str_contains($htmlBody, 'page cannot be displayed')) {
            $body = $pythonBodys[3];
        } elseif (str_contains($htmlBody, '401 unauthorized')) {
            $body = '要账号密码登录';
        } elseif (str_contains($htmlBody, '403 forbidden')) {
            $body = '访问被拒绝';
        } elseif (str_contains($htmlBody, 'account suspended') || str_contains($htmlBody, 'account temporary on hold')) {
            $body = '账号被停用';
        } elseif (str_contains($htmlBody, 'planethoster - suspendu')) {
            $body = '站点被停用';
        } elseif (str_contains($htmlBody, 'internal server error')) {
            $body = '服务端错误';
        } elseif (str_contains($htmlBody, 'lease wait while your request is being verified')) {
            $body = $pythonBodys[1];
        } elseif (str_contains($htmlBody, 'under construction') || str_contains($htmlBody, '<title>coming soon</title>')) {
            $body = '空站点'; // 网站建设中
        } elseif (str_contains($htmlBody, '<div class="loader" id="sk-loader"></div>')) {
            $body = '劫持掉了';
        } elseif (str_contains($htmlBody, 'page not found') ||
            str_contains($htmlBody, '<title>404 not found</title>') ||
            str_contains($htmlBody, '<title>404 Not') ||
            str_contains($htmlBody, '<title>404 not') ||
            str_contains($htmlBody, 'class="error404 ') ||
            str_contains($htmlBody, 'The resource requested could not be found on this server!') ||
            str_contains($htmlBody, 'page doesn\'t seem to exist') ||
            str_contains($htmlBody, '<title>404 - PAGE NOT FOUND</title>') ||
            str_contains($htmlBody, '<p>It looks like nothing was found at this location.</p>') ||
            str_contains($htmlBody, '<h1><H2>404 ERROR </H2></h1>') ||
            str_contains($htmlBody, '<title>Page not found')
        ) {
            $body = '网页不存在';
        } elseif (str_contains($htmlBody, ' lang="en"') || str_contains($htmlBody, ' lang="es"')
            || str_contains($htmlBody, ' lang="pt-br"') || str_contains($htmlBody, ' lang="fa"')
            || str_contains($htmlBody, ' lang="en-us"') || str_contains($htmlBody, 'html lang="ru"')
            || str_contains($htmlBody, ' lang="en-au"') || str_contains($htmlBody, 'html lang="fr-fr"')
            || str_contains($htmlBody, ' lang="ua"') || str_contains($htmlBody, ' lang="en-US"')
            || str_contains($htmlBody, ' lang="en-NZ"') || str_contains($htmlBody, ' lang="fr-FR"')
            || str_contains($htmlBody, ' lang="hu"')
            || str_contains($htmlBody, '<head><script>function set_cookie(){var')
            || str_contains($htmlBody, 'charset=iso-8859-1')
            || str_contains($htmlBody, '<body><script type=\'text/javascript\'>window.location.replace')
            || str_contains($htmlBody, '<head><script>window.onload=function(){window.location.href=')
        ) {
            $body = '劫持掉了';
        } elseif (empty($htmlBody) || str_contains($htmlBody, 'you need to enable javascript to run this app')) {
            $body = '空页面';
        } elseif (str_contains($htmlBody, '<b>fatal error</b>') || str_contains($htmlBody, 'parse error: syntax error in') || str_contains($htmlBody, '<b>warning</b>:  ')) {
            $body = '网站有错误';
        }

        if (!$tiao && empty($body)) {
            if (str_starts_with($htmlBody, 'ok|')) {
                $ret = Consts::RET_SUCCESS;
                $body = $this->_okfun(mb_substr($htmlBody, 0, $maxlen));
            } elseif ($lang !== 'ja') {
                $body = '劫持掉了';
            } else {
                $count1 = substr_count($htmlBody, '.xml');
                $count2 = substr_count($htmlBody, '<url>');
                if ($count1 > 10 || $count2 > 30) {
                    $body = '同行占领-XML';
                } elseif (str_contains($htmlBody, '<title>') && str_contains($htmlBody, '<meta') && (str_contains($htmlBody, ' lang="ja"') || str_contains($htmlBody, 'content="ja_jp"')) && (str_contains($htmlBody, '円') || str_contains($htmlBody, '¥'))) {
                    $body = '同行占领-商品页';
                } elseif (str_contains($htmlBody, 'website unavailable')) {
                    $body = '网站不可用';
                }
            }
        }

        return compact('ret', 'body');
    }

    /**
     * 发起检查域名从curl
     * 返回ret和body
     */
    protected function _curlCheckDomain(array $arr, $container, bool $isProxy = true)
    {
        // 定义一些变量
        $proxy = intval($isProxy);
        $domain = $arr['domain'];
        $kaitou = $domain;
        if (!str_starts_with($domain, 'http')) {
            // 防止高并发时443和80端口一起混用，这边直接试用https
            $kaitou = 'https://' . $arr['domain'];
        }
        $body = '';
        // 默认是失败
        $ret = Consts::RET_FAILURE;
        $maxlen = 15;
        $maxConnections = 2000;
        $timeout = 30;
        $readTimeout = 25;
        $guzzle = $container->get(\Hyperf\Guzzle\ClientFactory::class);
        $handlerStack = \GuzzleHttp\HandlerStack::create(new CurlHandler([
            'max_connections' => $maxConnections,
        ]));
        // 添加重试中间件, 默认2次
        $handlerStack->push(GuzzleRetryMiddleware::create());

        // 需要python去跑的body
        $pythonBodys = [
            '被判定为机器人，请手动访问',
            '要手动验证',
            '被判定高危站点，请手动访问',
            '被判定隐私站，请手动访问',
            '异常-请管理员排查',
            '被WAF拦截'
        ];

        try {
            $resqBody = [
                'handler' => $handlerStack,
                'connect_timeout' => 5,// 连接超时时间
                'timeout' => $timeout,// 总超时时间
                'read_timeout' => $readTimeout,  // 读取超时时间
                'verify' => false, // 如果是自签名证书可关闭验证（生产环境慎用）
                // 'verify' => BASE_PATH . '/cacert.pem',
                'http_errors' => false,// 不抛出HTTP错误异常
                'allow_redirects' => true, // 默认最多5词的重定向
                'headers' => [
                    'Connection' => 'keep-alive', // 保持连接复用
                    'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
                ],
                'curl' => [
                    CURLOPT_FORBID_REUSE => false, // 允许连接复用
                    CURLOPT_FRESH_CONNECT => false, // 不使用新连接
                ],
            ];
            if ($isProxy) {
                $resqBody['proxy'] = 'http://wyxychenzhijiang:7SeaMpDegg@146.103.51.241:59100';
            }
            $resp = @$guzzle->create($resqBody)->request('GET', rtrim($kaitou, '/') . '/sxallsitemap.xml');
            $code = $resp->getStatusCode();
            $htmlBody = strtolower((string)$resp->getBody());
            $htmlBody2 = '--';
            $bodylen = mb_strlen($htmlBody);
            if ($bodylen < 50) {
                $htmlBody2 = str_replace(["\n", "\r", "\t", ' '], '', $htmlBody);
            }

            if ($code !== 200) {
                if ($code === 301) {
                    $body = '永久重定向';
                } elseif ($code === 302) {
                    $body = '临时重定向';
                } elseif ($code === 401) {
                    $body = '访问被拒绝';
                } elseif ($code === 403) {
                    $body = '访问被拒绝';
                } elseif ($code === 404) {
                    $body = '网页不存在';
                } elseif ($code === 406 && str_contains($htmlBody, 'not acceptable!')) {
                    $body = $pythonBodys[5];
                } elseif ($code >= 500) {
                    $body = '服务端错误';
                } elseif ($code === 0) {
                    $body = $pythonBodys[4];
                }
                if (!empty($body) && !in_array($body, $pythonBodys)) {
                    return compact('ret', 'body', 'proxy');
                }
            } else {
                // http_code=200时
                $bodyRet = $this->getBodyByHtml($htmlBody, $maxlen, $pythonBodys);
                $ret = $bodyRet['ret'];
                $body = $bodyRet['body'];

                if (!empty($body) && !in_array($body, $pythonBodys)) {
                    // 包含成功的
                    return compact('ret', 'body', 'proxy');
                }
            }

            // 剩下是其他一些情况时，比如那种code返回404,但是内容又是ok|开头的
            $ret = Consts::RET_FAILURE;
            empty($body) && $body = '请手动排查';
            if (str_starts_with($htmlBody, 'ok|')) {
                $ret = Consts::RET_SUCCESS;
                $body = $this->_okfun(mb_substr($htmlBody, 0, $maxlen));
            } elseif (str_starts_with($htmlBody2, 'ok')) {
                $ret = Consts::RET_SUCCESS;
                $body = $this->_okfun(mb_substr($htmlBody2, 0, $maxlen));;
            } elseif ($body === '请手动排查' && $isProxy) {
                // 去掉代理再请求一次
                logger()->info('不走代理查询1，domain=' . $domain);
                return $this->_curlCheckDomain($arr, $container, false);
            } elseif (!empty($body) && in_array($body, $pythonBodys)) {
                // 明确只能用python跑，就丢给python跑
                return $this->pyCheckDomain($domain, $maxlen);
            }

            // 剩余非ok的返回
            logger()->info('domain=' . $domain . ',ret=' . $ret . ',body=' . $body . ',proxy=' . $proxy);
            return compact('ret', 'body', 'proxy');
        } catch (\Throwable $e) {
            $ret = Consts::RET_FAILURE;
            $emsg = $e->getMessage();
            $msg = '异常了,msg=' . $emsg . ',file:' . $e->getFile() . ',line:' . $e->getLine() . ',proxy=' . $proxy;
            logger()->error($msg, $arr);

            // 如果是curl请求超时，则直接提示，不再走python了
            if (str_contains($emsg, 'Operation timed out after')) {
                $body = '超时' . $timeout . '秒';
            } elseif (str_contains($emsg, 'cURL error 52: Empty reply from server')) {
                $body = '无法连接';
            } elseif (str_contains($emsg, 'cURL error 35: OpenSSL')) {
                $body = 'SSL协议错误';
            } elseif (str_contains($emsg, 'cURL error 110: Connection timed out')) {
                // 不知道是否是网络问题，这种问题我本地有的能访问到。或者隔一段时间它又可以了
                if (env('APP_ENV') !== 'dev') {
                    $cmd = <<<CMD
curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 -m 8 -x http://wyxychenzhijiang:7SeaMpDegg@146.103.51.241:59100 
CMD;
                    $httpCode = intval(@shell_exec($cmd . $kaitou));
                    if ($httpCode >= 500) {
                        $body = '服务端错误';
                    } elseif ($httpCode >= 400) {
                        $body = '拒接请求';
                    } elseif ($httpCode < 200) {
                        $body = '无法连接';
                    } else {
                        // 置空，去走python处理
                        $body = '';
                    }
                } else {
                    $body = '连接超时';
                }
            } elseif (str_contains($emsg, 'cURL error 104: Connection reset by peer')) {
                $body = '连接关闭';
            } elseif (str_contains($emsg, 'cURL error 24: Too many open files')) {
                $body = '并发过高请稍后查';
            } elseif (str_contains($emsg, 'cURL error 56: CONNECT tunnel failed, response 500')) {
                // 在通过 HTTP 代理建立 CONNECT 隧道时失败，代理服务器返回了 500 内部服务器错误
                // 去掉代理再请求一次
                logger()->info('不走代理查询2，domain=' . $domain);
                return $this->_curlCheckDomain($arr, $container, false);
            }

            if (empty($body)) {
                // 其他一些异常，比如如下：
                // cURL error 28: Proxy CONNECT aborted due to timeout
                // cURL error 52: Empty reply from server
                // Will not follow more than 5 redirects
                // cURL error 56: CONNECT tunnel failed, response 500
                return $this->pyCheckDomain($domain, $maxlen);
            }

            return compact('ret', 'body', 'proxy');
        }
    }

    /**
     * python调用去查
     */
    public function pyCheckDomain(string $domain, int $maxlen)
    {
        // python也是走代理的，如果是docker环境，其实调用不到本地的python程序
        logger()->error('走python,domain=' . $domain . ',cmd=' . env('APP_PYTHON') . $domain);
        $ret = Consts::RET_FAILURE;
        $proxy = 1;
        if (env('APP_ENV') === 'dev') {
            // 本地环境暂时直接返回，不要时注释下面三行
            $proxy = 0;
            $body = 'err|请手动排查';
            return compact('ret', 'body', 'proxy');
        }

        // TODO：查看是否已经有python在跑这个域名了（避免量很多时，一时没查出来，会再次过来打开），如果有

        $body = @shell_exec(env('APP_PYTHON') . $domain);
        $body = str_replace(["\n", "\r", "\t", ' '], '', (string)$body);
        logger()->error('走python结果,domain=' . $domain . ',body=' . $body);

        if (!empty($body) && str_starts_with($body, 'ok|')) {
            $ret = Consts::RET_SUCCESS;
            $body = $this->_okfun(mb_substr($body, 0, $maxlen));
        } elseif (empty($body)) {
            $body = 'err|请手动排查';
        } else {
            $body = mb_substr($body, 0, $maxlen);
        }
        return compact('ret', 'body', 'proxy');
    }

    /**
     * 检查域名
     */
    public function doCheckDomain(array $arr, $container)
    {
        // 定义消息返回的数据
        $retfn = function (array $array, string $ret = Consts::RET_GENING, string $body = '等待执行', int $proxy = 0) {
            return [
                'index' => $array['index'] ?? 0,
                'domain' => $array['domain'],
                'ret' => $ret,
                'body' => $body,
                'proxy' => $proxy,
                'datetime' => date('YmdHis')
            ];
        };

        // 验证域名等参数
        $domain = $arr['domain'];
        if ($domain === '域名') {
            return $retfn($arr, Consts::RET_FAILURE, '域名错误');
        }
        if (empty($arr['index'])) {
            return $retfn($arr, Consts::RET_FAILURE, 'index参数错误');
        }

        $curlRet = $this->_curlCheckDomain($arr, $container);
        return $retfn($arr, $curlRet['ret'], $curlRet['body'], $curlRet['proxy']);
    }

    /**
     * 检查爬虫
     */
    public function doSpiderCheck(array $arr, $container)
    {
        // 定义消息返回的数据
        $retfn = function (array $array, string $ret = Consts::RET_GENING, string $body = '等待执行', int $proxy = 0) {
            return [
                'index' => $array['index'] ?? 0,
                'domain' => $array['domain'],
                'ret' => $ret,
                'body' => $body,
                'proxy' => $proxy,
                'datetime' => date('YmdHis')
            ];
        };

        // 验证域名等参数
        $domain = $arr['domain'];
        if ($domain === '域名') {
            return $retfn($arr, Consts::RET_FAILURE, '域名错误');
        }
        if (empty($arr['index'])) {
            return $retfn($arr, Consts::RET_FAILURE, 'index参数错误');
        }

        $curlRet = $this->_curlSpiderDomain($arr, $container);
        return $retfn($arr, $curlRet['ret'], $curlRet['body'], $curlRet['proxy']);
    }

    /**
     * 发起检查域名从curl
     * 返回ret和body
     */
    protected function _curlSpiderDomain(array $arr, $container, bool $isProxy = true)
    {
        // 定义一些变量
        $proxy = intval($isProxy);
        $domain = $arr['domain'];
        $maxlen = 15;
        $kaitou = $domain;
        if (!str_starts_with($domain, 'http')) {
            // 防止高并发时443和80端口一起混用，这边直接试用https
            $kaitou = 'https://' . $arr['domain'];
        }
        // 默认是失败
        $ret = Consts::RET_FAILURE;
        $maxConnections = 2000;
        $timeout = 30;
        $readTimeout = 25;
        $guzzle = $container->get(\Hyperf\Guzzle\ClientFactory::class);
        $handlerStack = \GuzzleHttp\HandlerStack::create(new CurlHandler([
            'max_connections' => $maxConnections,
        ]));
        // 添加重试中间件, 默认2次
        $handlerStack->push(GuzzleRetryMiddleware::create());
        // 需要python去跑的body
        $pythonBodys = [
            '被判定为机器人，请手动访问',
            '要手动验证',
            '被判定高危站点，请手动访问',
            '被判定隐私站，请手动访问',
            '异常-请管理员排查',
            '被WAF拦截'
        ];

        try {
            $resqBody = [
                'handler' => $handlerStack,
                'connect_timeout' => 5,// 连接超时时间
                'timeout' => $timeout,// 总超时时间
                'read_timeout' => $readTimeout,  // 读取超时时间
                'verify' => false, // 如果是自签名证书可关闭验证（生产环境慎用）
                // 'verify' => BASE_PATH . '/cacert.pem',
                'http_errors' => false,// 不抛出HTTP错误异常
                'allow_redirects' => true, // 默认最多5词的重定向
                'headers' => [
                    'Connection' => 'keep-alive', // 保持连接复用
                    'User-Agent' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
                ],
                'curl' => [
                    CURLOPT_FORBID_REUSE => false, // 允许连接复用
                    CURLOPT_FRESH_CONNECT => false, // 不使用新连接
                ],
            ];
            if ($isProxy) {
                $resqBody['proxy'] = 'http://wyxychenzhijiang:7SeaMpDegg@146.103.51.241:59100';
            }
            $resp = @$guzzle->create($resqBody)->request('GET', rtrim($kaitou, '/') . '/123456.html');
            $code = $resp->getStatusCode();
            $htmlBody = strtolower((string)$resp->getBody());
            $bodylen = mb_strlen($htmlBody);
            if ($code == 200 && $bodylen > 2000) {
                $curlRet = $this->_curlCheckDomain($arr, $container);
                $bodyRet = $this->getBodyByHtml($htmlBody, $maxlen, $pythonBodys, true);
                if ($curlRet['ret'] == Consts::RET_SUCCESS && empty($bodyRet['body'])) {
                    $ret = Consts::RET_SUCCESS;
                    $body = 'ok';
                } else {
                    if ($bodyRet['ret'] == Consts::RET_FAILURE && !empty($bodyRet['body'])) {
                        $ret = Consts::RET_FAILURE;
                        $body = $bodyRet['body'];
                    } else {
                        $ret = Consts::RET_SUCCESS;
                        $body = '爬虫OK，但-' . $curlRet['body'];
                    }
                }
            } elseif ($bodylen === 0) {
                $body = '空页面-';
            } elseif ($code === 301) {
                $body = '永久重定向';
            } elseif ($code === 302) {
                $body = '临时重定向';
            } elseif ($code === 401) {
                $body = '访问被拒绝';
            } elseif ($code === 403) {
                $body = '访问被拒绝';
            } elseif ($code === 404) {
                $body = '网页不存在';
            } elseif ($code === 406 && str_contains($htmlBody, 'not acceptable!')) {
                $body = '被WAF拦截';
            } elseif ($code > 500) {
                $body = '服务端错误';
            } else {
                logger()->error($domain . ',bodylen=' . $bodylen . ',code=' . $code . ',html=' . mb_substr($htmlBody, 0, 300), []);
                $body = '爬不到页面或其他错误';
            }
            return compact('ret', 'body', 'proxy');
        } catch (\Throwable $e) {
            $ret = Consts::RET_FAILURE;
            $emsg = $e->getMessage();
            $msg = 'spider异常了,msg=' . $emsg . ',file:' . $e->getFile() . ',line:' . $e->getLine() . ',proxy=' . $proxy;
            logger()->error($msg, $arr);
            // 如果发现是代理引起的异常，则不用代理再试一次
            if ($proxy && str_contains($emsg, 'cURL error 7104: wrong http_proxy response received')) {
                return $this->_curlSpiderDomain($arr, $container, false);
            }
            $body = '异常:' . mb_substr($emsg, 0, 20);
            return compact('ret', 'body', 'proxy');
        }
    }

    public function onMessage($server, $frame): void
    {
        $response = (new Response($server))->init($frame);
        if ($frame->opcode == Opcode::PING) {
            // 如果使用协程 Server，在判断是 PING 帧后，需要手动处理，返回 PONG 帧。
            // 异步风格 Server，可以直接通过 Swoole 配置处理，详情请见 https://wiki.swoole.com/#/websocket_server?id=open_websocket_ping_frame
            $response->push(new Frame(opcode: Opcode::PONG));
            return;
        }
        if ($this->isConnectionActive($server, $frame->fd)) {
            $arr = @json_decode($frame->data, true);
            if (!empty($arr)) {
                $container = ApplicationContext::getContainer();
                // var_dump('ws进来的数据2', $arr);

                if ($arr['flag'] === 'group_check' && !empty($arr['domain'])) {
                    $resp = $this->doCheckDomain($arr, $container);
                    $response->push(new Frame(payloadData: json_encode($resp, JSON_UNESCAPED_UNICODE)));
                    return;
                } elseif ($arr['flag'] === 'spider_check' && !empty($arr['domain'])) {
                    $resp = $this->doSpiderCheck($arr, $container);
                    $response->push(new Frame(payloadData: json_encode($resp, JSON_UNESCAPED_UNICODE)));
                } elseif ($arr['flag'] === 'domain_level' && !empty($arr['domain'])) {
                    $kaitou = strtolower(trim($arr['domain']));
                    // 单个词
                    $suffixs1 = [
                        'aaa', 'aarp', 'abarth', 'abb', 'abbott', 'abbvie', 'abc', 'able', 'abogado', 'abudhabi', 'ac', 'academy', 'accenture', 'accountant', 'accountants', 'aco', 'active',
                        'actor', 'ad', 'adac', 'ads', 'adult', 'ae', 'aeg', 'aero', 'aetna', 'af', 'afamilycompany', 'afl', 'africa', 'ag', 'agakhan', 'agency', 'ai', 'aig', 'aigo', 'airbus',
                        'airforce', 'airtel', 'akdn', 'al', 'alfaromeo', 'alibaba', 'alipay', 'allfinanz', 'allstate', 'ally', 'alsace', 'alstom', 'am', 'amazon', 'americanexpress',
                        'americanfamily', 'amex', 'amfam', 'amica', 'amsterdam', 'an', 'analytics', 'android', 'anquan', 'anz', 'ao', 'aol', 'apartments', 'app', 'apple', 'aq', 'aquarelle',
                        'ar', 'arab', 'aramco', 'archi', 'army', 'arpa', 'art', 'arte', 'as', 'asda', 'asia', 'associates', 'at', 'athleta', 'attorney', 'au', 'auction', 'audi', 'audible',
                        'audio', 'auspost', 'author', 'auto', 'autos', 'avianca', 'aw', 'aws', 'ax', 'axa', 'az', 'azure',
                        'ba', 'baby', 'baidu', 'banamex', 'bananarepublic', 'band', 'bank', 'bar', 'barcelona', 'barclaycard', 'barclays', 'barefoot', 'bargains', 'baseball', 'basketball',
                        'bauhaus', 'bayern', 'bb', 'bbc', 'bbt', 'bbva', 'bcg', 'bcn', 'bd', 'be', 'beats', 'beauty', 'beer', 'bentley', 'berlin', 'best', 'bestbuy', 'bet', 'bf', 'bg', 'bh',
                        'bharti', 'bi', 'bible', 'bid', 'bike', 'bing', 'bingo', 'bio', 'biz', 'bj', 'bl', 'black', 'blackfriday', 'blanco', 'blockbuster', 'blog', 'bloomberg', 'blue', 'bm',
                        'bms', 'bmw', 'bn', 'bnl', 'bnpparibas', 'bo', 'boats', 'boehringer', 'bofa', 'bom', 'bond', 'boo', 'book', 'booking', 'boots', 'bosch', 'bostik', 'boston', 'bot',
                        'boutique', 'box', 'bq', 'br', 'bradesco', 'bridgestone', 'broadway', 'broker', 'brother', 'brussels', 'bs', 'bt', 'budapest', 'bugatti', 'build', 'builders',
                        'business', 'buy', 'buzz', 'bv', 'bw', 'by', 'bz', 'bzh', 'ca', 'cab', 'cafe', 'cal', 'call', 'calvinklein', 'cam', 'camera', 'camp', 'cancerresearch', 'canon',
                        'capetown', 'capital', 'capitalone', 'car', 'caravan', 'cards', 'care', 'career', 'careers', 'cars', 'cartier', 'casa', 'case', 'caseih', 'cash', 'casino', 'cat',
                        'catering', 'catholic', 'cba', 'cbn', 'cbre', 'cbs', 'cc', 'cd', 'ceb', 'center', 'ceo', 'cern', 'cf', 'cfa', 'cfd', 'cg', 'ch', 'chanel', 'channel', 'charity', 'chase',
                        'chat', 'cheap', 'chintai', 'chloe', 'christmas', 'chrome', 'chrysler', 'church', 'ci', 'cipriani', 'circle', 'cisco', 'citadel', 'citi', 'citic', 'city', 'cityeats',
                        'ck', 'cl', 'claims', 'cleaning', 'click', 'clinic', 'clinique', 'clothing', 'cloud', 'club', 'clubmed', 'cm', 'cn', 'co', 'coach', 'codes', 'coffee', 'college',
                        'cologne', 'com', 'comcast', 'commbank', 'community', 'company', 'compare', 'computer', 'comsec', 'condos', 'construction', 'consulting', 'contact', 'contractors',
                        'cooking', 'cookingchannel', 'cool', 'coop', 'corsica', 'country', 'coupon', 'coupons', 'courses', 'cpa', 'cr', 'credit', 'creditcard', 'creditunion', 'cricket',
                        'crown', 'crs', 'cruise', 'cruises', 'csc', 'cu', 'cuisinella', 'cv', 'cw', 'cx', 'cy', 'cymru', 'cyou', 'cz',
                        'dabur', 'dad', 'dance', 'data', 'date', 'dating', 'datsun', 'day', 'dclk', 'dds', 'de', 'deal', 'dealer', 'deals', 'degree', 'delivery', 'dell', 'deloitte', 'delta',
                        'democrat', 'dental', 'dentist', 'desi', 'design', 'dev', 'dhl', 'diamonds', 'diet', 'digital', 'direct', 'directory', 'discount', 'discover', 'dish', 'diy', 'dj',
                        'dk', 'dm', 'dnp', 'do', 'docs', 'doctor', 'dodge', 'dog', 'doha', 'domains', 'doosan', 'dot', 'download', 'drive', 'dtv', 'dubai', 'duck', 'dunlop', 'duns', 'dupont',
                        'durban', 'dvag', 'dvr', 'dz', 'earth', 'eat', 'ec', 'eco', 'edeka', 'edu', 'education', 'ee', 'eg', 'eh', 'email', 'emerck', 'emerson', 'energy', 'engineer',
                        'engineering', 'enterprises', 'epost', 'epson', 'equipment', 'er', 'ericsson', 'erni', 'es', 'esq', 'estate', 'esurance', 'et', 'etisalat', 'eu', 'eurovision',
                        'eus', 'events', 'everbank', 'exchange', 'expert', 'exposed', 'express', 'extraspace',
                        'fage', 'fail', 'fairwinds', 'faith', 'family', 'fan', 'fans', 'farm', 'farmers', 'fashion', 'fast', 'fedex', 'feedback', 'ferrari', 'ferrero', 'fi', 'fiat',
                        'fidelity', 'fido', 'film', 'final', 'finance', 'financial', 'fire', 'firestone', 'firmdale', 'fish', 'fishing', 'fit', 'fitness', 'fj', 'fk', 'flickr', 'flights',
                        'flir', 'florist', 'flowers', 'flsmidth', 'fly', 'fm', 'fo', 'foo', 'food', 'foodnetwork', 'football', 'ford', 'forex', 'forsale', 'forum', 'foundation', 'fox',
                        'fr', 'free', 'fresenius', 'frl', 'frogans', 'frontdoor', 'frontier', 'ftr', 'fujitsu', 'fujixerox', 'fun', 'fund', 'furniture', 'futbol', 'fyi',
                        'ga', 'gal', 'gallery', 'gallo', 'gallup', 'game', 'games', 'gap', 'garden', 'gay', 'gb', 'gbiz', 'gd', 'gdn', 'ge', 'gea', 'gent', 'genting', 'george', 'gf', 'gg',
                        'ggee', 'gh', 'gi', 'gift', 'gifts', 'gives', 'giving', 'gl', 'glade', 'glass', 'gle', 'global', 'globo', 'gm', 'gmail', 'gmbh', 'gmo', 'gmx', 'gn', 'godaddy', 'gold',
                        'goldpoint', 'golf', 'goo', 'goodhands', 'goodyear', 'goog', 'google', 'gop', 'got', 'gov', 'gp', 'gq', 'gr', 'grainger', 'graphics', 'gratis', 'green', 'gripe',
                        'grocery', 'group', 'gs', 'gt', 'gu', 'guardian', 'gucci', 'guge', 'guide', 'guitars', 'guru', 'gw', 'gy',
                        'hair', 'hamburg', 'hangout', 'haus', 'hbo', 'hdfc', 'hdfcbank', 'health', 'healthcare', 'help', 'helsinki', 'here', 'hermes', 'hgtv', 'hiphop', 'hisamitsu',
                        'hitachi', 'hiv', 'hk', 'hkt', 'hm', 'hn', 'hockey', 'holdings', 'holiday', 'homedepot', 'homegoods', 'homes', 'homesense', 'honda', 'honeywell', 'horse',
                        'hospital', 'host', 'hosting', 'hot', 'hoteles', 'hotels', 'hotmail', 'house', 'how', 'hr', 'hsbc', 'ht', 'htc', 'hu', 'hughes', 'hyatt', 'hyundai',
                        'ibm', 'icbc', 'ice', 'icu', 'id', 'ie', 'ieee', 'ifm', 'iinet', 'ikano', 'il', 'im', 'imamat', 'imdb', 'immo', 'immobilien', 'in', 'inc', 'industries', 'infiniti',
                        'info', 'ing', 'ink', 'institute', 'insurance', 'insure', 'int', 'intel', 'international', 'intuit', 'investments', 'io', 'ipiranga', 'iq', 'ir', 'irish', 'is',
                        'iselect', 'ismaili', 'ist', 'istanbul', 'it', 'itau', 'itv', 'iveco', 'iwc',
                        'jaguar', 'java', 'jcb', 'jcp', 'je', 'jeep', 'jetzt', 'jewelry', 'jio', 'jlc', 'jll', 'jm', 'jmp', 'jnj', 'jo', 'jobs', 'joburg', 'jot', 'joy', 'jp', 'jpmorgan', 'jprs', 'juegos', 'juniper',
                        'kaufen', 'kddi', 'ke', 'kerryhotels', 'kerrylogistics', 'kerryproperties', 'kfh', 'kg', 'kh', 'ki', 'kia', 'kids', 'kim', 'kinder', 'kindle', 'kitchen', 'kiwi',
                        'km', 'kn', 'koeln', 'komatsu', 'kosher', 'kp', 'kpmg', 'kpn', 'kr', 'krd', 'kred', 'kuokgroup', 'kw', 'ky', 'kyoto', 'kz',
                        'la', 'lacaixa', 'ladbrokes', 'lamborghini', 'lamer', 'lancaster', 'lancia', 'lancome', 'land', 'landrover', 'lanxess', 'lasalle', 'lat', 'latino', 'latrobe',
                        'law', 'lawyer', 'lb', 'lc', 'lds', 'lease', 'leclerc', 'lefrak', 'legal', 'lego', 'lexus', 'lgbt', 'li', 'liaison', 'lidl', 'life', 'lifeinsurance', 'lifestyle',
                        'lighting', 'like', 'lilly', 'limited', 'limo', 'lincoln', 'linde', 'link', 'lipsy', 'live', 'living', 'lixil', 'lk', 'llc', 'llp', 'loan', 'loans', 'locker', 'locus',
                        'loft', 'lol', 'london', 'lotte', 'lotto', 'love', 'lpl', 'lplfinancial', 'lr', 'ls', 'lt', 'ltd', 'ltda', 'lu', 'lundbeck', 'lupin', 'luxe', 'luxury', 'lv', 'ly',
                        'ma', 'macys', 'madrid', 'maif', 'maison', 'makeup', 'man', 'management', 'mango', 'map', 'market', 'marketing', 'markets', 'marriott', 'marshalls', 'maserati',
                        'mattel', 'mba', 'mc', 'mcd', 'mcdonalds', 'mckinsey', 'md', 'me', 'med', 'media', 'meet', 'melbourne', 'meme', 'memorial', 'men', 'menu', 'meo', 'merckmsd',
                        'metlife', 'mf', 'mg', 'mh', 'miami', 'microsoft', 'mil', 'mini', 'mint', 'mit', 'mitsubishi', 'mk', 'ml', 'mlb', 'mls', 'mm', 'mma', 'mn', 'mo', 'mobi', 'mobile',
                        'mobily', 'moda', 'moe', 'moi', 'mom', 'monash', 'money', 'monster', 'montblanc', 'mopar', 'mormon', 'mortgage', 'moscow', 'moto', 'motorcycles', 'mov', 'movie',
                        'movistar', 'mp', 'mq', 'mr', 'ms', 'msd', 'mt', 'mtn', 'mtpc', 'mtr', 'mu', 'museum', 'music', 'mutual', 'mutuelle', 'mv', 'mw', 'mx', 'my', 'mz',
                        'na', 'nab', 'nadex', 'nagoya', 'name', 'nationwide', 'natura', 'navy', 'nba', 'nc', 'ne', 'nec', 'net', 'netbank', 'netflix', 'network', 'neustar', 'new',
                        'newholland', 'news', 'next', 'nextdirect', 'nexus', 'nf', 'nfl', 'ng', 'ngo', 'nhk', 'ni', 'nico', 'nike', 'nikon', 'ninja', 'nissan', 'nissay', 'nl', 'no', 'nokia',
                        'northwesternmutual', 'norton', 'now', 'nowruz', 'nowtv', 'np', 'nr', 'nra', 'nrw', 'ntt', 'nu', 'nyc', 'nz',
                        'obi', 'observer', 'off', 'office', 'okinawa', 'olayan', 'olayangroup', 'oldnavy', 'ollo', 'om', 'omega', 'one', 'ong', 'onl', 'online', 'onyourside', 'ooo',
                        'open', 'oracle', 'orange', 'org', 'organic', 'orientexpress', 'origins', 'osaka', 'otsuka', 'ott', 'ovh',
                        'pa', 'page', 'pamperedchef', 'panasonic', 'panerai', 'paris', 'pars', 'partners', 'parts', 'party', 'passagens', 'pay', 'pccw', 'pe', 'pet', 'pf', 'pfizer', 'pg',
                        'ph', 'pharmacy', 'phd', 'philips', 'phone', 'photo', 'photography', 'photos', 'physio', 'piaget', 'pics', 'pictet', 'pictures', 'pid', 'pin', 'ping', 'pink',
                        'pioneer', 'pizza', 'pk', 'pl', 'place', 'play', 'playstation', 'plumbing', 'plus', 'pm', 'pn', 'pnc', 'pohl', 'poker', 'politie', 'porn', 'post', 'pr', 'pramerica',
                        'praxi', 'press', 'prime', 'pro', 'prod', 'productions', 'prof', 'progressive', 'promo', 'properties', 'property', 'protection', 'pru', 'prudential', 'ps', 'pt',
                        'pub', 'pw', 'pwc', 'py', 'qa', 'qpon', 'quebec', 'quest', 'qvc',
                        'racing', 'radio', 'raid', 're', 'read', 'realestate', 'realtor', 'realty', 'recipes', 'red', 'redstone', 'redumbrella', 'rehab', 'reise', 'reisen', 'reit',
                        'reliance', 'ren', 'rent', 'rentals', 'repair', 'report', 'republican', 'rest', 'restaurant', 'review', 'reviews', 'rexroth', 'rich', 'richardli', 'ricoh',
                        'rightathome', 'ril', 'rio', 'rip', 'rmit', 'ro', 'rocher', 'rocks', 'rodeo', 'rogers', 'room', 'rs', 'rsvp', 'ru', 'rugby', 'ruhr', 'run', 'rw', 'rwe', 'ryukyu',
                        'sa', 'saarland', 'safe', 'safety', 'sakura', 'sale', 'salon', 'samsclub', 'samsung', 'sandvik', 'sandvikcoromant', 'sanofi', 'sap', 'sapo', 'sarl', 'sas',
                        'save', 'saxo', 'sb', 'sbi', 'sbs', 'sc', 'sca', 'scb', 'schaeffler', 'schmidt', 'scholarships', 'school', 'schule', 'schwarz', 'science', 'scjohnson', 'scor',
                        'scot', 'sd', 'se', 'search', 'seat', 'secure', 'security', 'seek', 'select', 'sener', 'services', 'ses', 'seven', 'sew', 'sex', 'sexy', 'sfr', 'sg', 'sh',
                        'shangrila', 'sharp', 'shaw', 'shell', 'shia', 'shiksha', 'shoes', 'shop', 'shopping', 'shouji', 'show', 'showtime', 'shriram', 'si', 'silk', 'sina', 'singles',
                        'site', 'sj', 'sk', 'ski', 'skin', 'sky', 'skype', 'sl', 'sling', 'sm', 'smart', 'smile', 'sn', 'sncf', 'so', 'soccer', 'social', 'softbank', 'software', 'sohu',
                        'solar', 'solutions', 'song', 'sony', 'soy', 'spa', 'space', 'spiegel', 'sport', 'spot', 'spreadbetting', 'sr', 'srl', 'srt', 'ss', 'st', 'stada', 'staples',
                        'star', 'starhub', 'statebank', 'statefarm', 'statoil', 'stc', 'stcgroup', 'stockholm', 'storage', 'store', 'stream', 'studio', 'study', 'style', 'su', 'sucks',
                        'supplies', 'supply', 'support', 'surf', 'surgery', 'suzuki', 'sv', 'swatch', 'swiftcover', 'swiss', 'sx', 'sy', 'sydney', 'symantec', 'systems', 'sz',
                        'tab', 'taipei', 'talk', 'taobao', 'target', 'tatamotors', 'tatar', 'tattoo', 'tax', 'taxi', 'tc', 'tci', 'td', 'tdk', 'team', 'tech', 'technology', 'tel',
                        'telecity', 'telefonica', 'temasek', 'tennis', 'teva', 'tf', 'tg', 'th', 'thd', 'theater', 'theatre', 'tiaa', 'tickets', 'tienda', 'tiffany', 'tips', 'tires',
                        'tirol', 'tj', 'tjmaxx', 'tjx', 'tk', 'tkmaxx', 'tl', 'tm', 'tmall', 'tn', 'to', 'today', 'tokyo', 'tools', 'top', 'toray', 'toshiba', 'total', 'tours', 'town',
                        'toyota', 'toys', 'tp', 'tr', 'trade', 'trading', 'training', 'travel', 'travelchannel', 'travelers', 'travelersinsurance', 'trust', 'trv', 'tt', 'tube',
                        'tui', 'tunes', 'tushu', 'tv', 'tvs', 'tw', 'tz',
                        'ua', 'ubank', 'ubs', 'uconnect', 'ug', 'uk', 'um', 'unicom', 'university', 'uno', 'uol', 'ups', 'us', 'uy', 'uz',
                        'va', 'vacations', 'vana', 'vanguard', 'vc', 've', 'vegas', 'ventures', 'verisign', 'versicherung', 'vet', 'vg', 'vi', 'viajes', 'video', 'vig', 'viking',
                        'villas', 'vin', 'vip', 'virgin', 'visa', 'vision', 'vista', 'vistaprint', 'viva', 'vivo', 'vlaanderen', 'vn', 'vodka', 'volkswagen', 'volvo', 'vote',
                        'voting', 'voto', 'voyage', 'vu', 'vuelos',
                        'wales', 'walmart', 'walter', 'wang', 'wanggou', 'warman', 'watch', 'watches', 'weather', 'weatherchannel', 'webcam', 'weber', 'website', 'wed', 'wedding',
                        'weibo', 'weir', 'wf', 'whoswho', 'wien', 'wiki', 'williamhill', 'win', 'windows', 'wine', 'winners', 'wme', 'wolterskluwer', 'woodside', 'work', 'works',
                        'world', 'wow', 'ws', 'wtc', 'wtf',
                        'xbox', 'xerox', 'xfinity', 'xihuan', 'xin', 'xperia', 'xxx', 'xyz',
                        'yachts', 'yahoo', 'yamaxun', 'yandex', 'ye', 'yodobashi', 'yoga', 'yokohama', 'you', 'youtube', 'yt', 'yun',
                        'za', 'zappos', 'zara', 'zero', 'zip', 'zippo', 'zm', 'zone', 'zuerich', '.zw',

                        // 可直接排除的，还有一些粘贴进来是乱码的，就已经直接排除在外了
                        // '测试', '佛山', '慈善', '集团', '在线', '大众汽车', '点看', '八卦', '公益', '公司', '香格里拉', '网站', '移动', '我爱你', '联通', '时尚', '微博', '淡马锡', '商标', '商店', '商城',
                        // '新闻', '工行', '家電', '中文网', '中信', '中国', '中國', '娱乐', '谷歌', '電訊盈科', '购物', '測試', '通販', '网店', '餐厅', '网络', '香港', '亚马逊', '诺基亚', '食品', '飞利浦', '台湾', '台灣',
                        // '手表', '手机', '澳門', '政府', '机构', '组织机构', '健康', '招聘', '珠宝', '大拿', '世界', '書籍', '网址', '天主教', '游戏', '企业', '信息', '嘉里大酒店', '嘉里', '广东', '新加坡', '政务',
                        // 'テスト', 'セール', 'ファッション', 'ストア', 'アマゾン', 'ポイント', 'クラウド', 'みんな', 'グーグル', 'コム', '삼성', '닷컴', '닷넷', '테스트',
                        // 'कॉम', 'परीक्षा', 'ಭಾರತ', '한국', 'ଭାରତ', 'คอม', 'ভাৰত', 'ভারত', 'বাংলা', 'भारतम्', 'भारत', 'भारोत', 'संगठन', 'नेट', 'சிங்கப்பூர்', 'இலங்கை', 'இந்தியா', 'ભારત', 'பரிட்சை', 'ഭാരതം', 'ਭਾਰਤ',
                        // 'δοκιμή', 'გე', 'ไทย', 'ລາວ', 'ευ', 'ελ', 'москва', 'испытание', 'қаз', 'католик', 'онлайн', 'сайт', 'дети', 'мкд', 'ею', 'рф', 'бг', 'бел',
                        // 'срб', 'орг', 'հայ', 'рус', 'vermögensberater', 'vermögensberatung', 'ком', 'укр', 'мон',
                    ];
                    // 多个，比如com.cn
                    $suffixs2 = [
                        'abo.pa', 'ac.id', 'ac.mu', 'ac.nz', 'ac.vn', 'ae.org', 'ai.vn',
                        'bbs.tr', 'biz.et', 'biz.id', 'biz.ki', 'biz.mm', 'biz.my', 'biz.pk', 'biz.pl', 'biz.pr', 'biz.tr', 'biz.vn', 'blog.br', 'br.com',
                        'club.tw', 'cn.com', 'co.ag', 'co.am', 'co.ao', 'co.at', 'co.bi', 'co.bw', 'co.bz', 'co.com', 'co.cr', 'co.dm', 'co.gl', 'co.gy', 'co.id',
                        'co.il', 'co.im', 'co.in', 'co.it', 'co.je', 'co.jp', 'co.ke', 'co.kr', 'co.lc', 'co.ls', 'co.mg', 'co.ms', 'co.mu', 'co.nl', 'co.no',
                        'co.nz', 'co.rw', 'co.th', 'co.tz', 'co.uk', 'co.uz', 'co.za', 'co.zm', 'co.zw', 'com.af', 'com.ag', 'com.ai', 'com.al', 'com.ar', 'com.au',
                        'com.bd', 'com.bi', 'com.bn', 'com.bo', 'com.br', 'com.bt', 'com.bz', 'com.ci', 'com.cm', 'com.cn', 'com.co', 'com.cy', 'com.de', 'com.do',
                        'com.ec', 'com.eg', 'com.es', 'com.et', 'com.ge', 'com.gh', 'com.gl', 'com.gp', 'com.gr', 'com.gt', 'com.gy', 'com.hk', 'com.hn', 'com.ht',
                        'com.im', 'com.jo', 'com.kg', 'com.ki', 'com.kz', 'com.lc', 'com.lk', 'com.ly', 'com.mg', 'com.mm', 'com.ms', 'com.mu', 'com.mw', 'com.mx',
                        'com.my', 'com.na', 'com.ne', 'com.nf', 'com.ng', 'com.nl', 'com.np', 'com.pa', 'com.pe', 'com.ph', 'com.pk', 'com.pl', 'com.pr', 'com.ps',
                        'com.pt', 'com.py', 'com.qa', 'com.ro', 'com.ru', 'com.sb', 'com.sc', 'com.se', 'com.sg', 'com.sn', 'com.so', 'com.sv', 'com.td', 'com.tl',
                        'com.tn', 'com.tr', 'com.tw', 'com.uz', 'com.vc', 'com.vn', 'com.zm', 'de.com',
                        'ebiz.tw', 'ed.cr', 'edu.bi', 'edu.ge', 'edu.gl', 'edu.gr', 'edu.lk', 'edu.my', 'edu.pa', 'edu.pl', 'edu.sg', 'edu.vn', 'esp.br', 'eu.com',
                        'far.br', 'fi.cr', 'fin.ec', 'game.tw', 'gb.net', 'geek.nz', 'gen.nz', 'gen.tr', 'go.cr', 'go.ke', 'gob.pa', 'gov.my', 'gr.com',
                        'health.vn', 'hu.net',
                        'id.ly', 'id.vn', 'idv.hk', 'idv.tw', 'ind.br', 'info.bi', 'info.et', 'info.ke', 'info.ki', 'info.nf', 'info.pl', 'info.pr', 'info.tr',
                        'info.vn', 'ing.pa', 'int.vn', 'io.vn', 'isla.pr', 'it.ao', 'it.com',
                        'jp.net', 'jpn.com', 'kiwi.nz', 'l.lc',
                        'maori.nz', 'me.ke', 'me.uk', 'med.ec', 'med.pa', 'mil.ge', 'mo.bi', 'mobi.ke', 'mobi.ki', 'mobi.ng', 'my.id',
                        'name.et', 'name.my', 'name.ng', 'name.pr', 'name.tr', 'name.vn', 'ne.ke', 'net.af', 'net.ag', 'net.ai', 'net.ar', 'net.au', 'net.bo', 'net.br',
                        'net.bz', 'net.cn', 'net.ec', 'net.et', 'net.ge', 'net.gl', 'net.gp', 'net.gr', 'net.gt', 'net.gy', 'net.hk', 'net.hn', 'net.ht', 'net.id',
                        'net.im', 'net.in', 'net.je', 'net.ki', 'net.lc', 'net.ly', 'net.mg', 'net.mu', 'net.my', 'net.nf', 'net.ng', 'net.nl', 'net.nz', 'net.pa',
                        'net.ph', 'net.pk', 'net.pl', 'net.pr', 'net.sb', 'net.sc', 'net.sg', 'net.so', 'net.td', 'net.tl', 'net.tr', 'net.tw', 'net.vc', 'net.vn',
                        'nom.ag', 'nom.es', 'nom.pa',
                        'or.at', 'or.bi', 'or.cr', 'or.id', 'or.ke', 'or.kr', 'org.af', 'org.ag', 'org.ai', 'org.au', 'org.bi', 'org.bo', 'org.br', 'org.bz',
                        'org.cn', 'org.do', 'org.ec', 'org.es', 'org.et', 'org.ge', 'org.gl', 'org.gp', 'org.gr', 'org.gt', 'org.hk', 'org.hn', 'org.ht', 'org.im',
                        'org.in', 'org.je', 'org.ki', 'org.kz', 'org.lc', 'org.lk', 'org.ly', 'org.mg', 'org.mm', 'org.ms', 'org.mu', 'org.my', 'org.ng', 'org.nz',
                        'org.pa', 'org.ph', 'org.pk', 'org.pl', 'org.pr', 'org.sb', 'org.sc', 'org.sg', 'org.so', 'org.td', 'org.tl', 'org.tr', 'org.tw', 'org.uk',
                        'org.uz', 'org.vc', 'org.vn', 'org.za', 'p.lc', 'per.mm', 'phone.ki', 'plc.ly', 'pro.ec', 'pro.pr', 'pro.vn', 'pvt.ge',
                        'ru.com', 'sa.com', 'sa.cr', 'sc.ke', 'sch.id', 'school.nz', 'se.net', 'sld.pa',
                        'tel.ki', 'tel.tr', 'tourism.td', 'tur.br', 'tv.bo', 'tv.br', 'tv.tr',
                        'uk.com', 'uk.net', 'us.com', 'us.org', 'waw.pl', 'web.id', 'web.tr', 'za.com',
                    ];

                    // 晓明指定不要的域名
                    $paichu = [
                        'suffix' => [
                            'tk', 'ml', 'ga', 'cf', 'gg', 'xyz', 'cn', 'top', 'site', 'shop', 'za.com', 'sa.com', 'ru.com', 'cfd', 'sbs', 'click', 'rest', 'buzz', 'world', 'cyou', 'asia', 'eu',
                            'digital', 'live', 'life', 'help', 'today', 'icu', 'pro', 'blog', 'info', 'us', 'bond', 'us'
                        ],
                        // 不要的开头
                        'prefix' => ['xn-']
                    ];

                    $shibie = '';
                    foreach ($paichu['prefix'] as $s) {
                        if (str_starts_with($kaitou, $s)) {
                            $shibie = '-不要的';
                            break;
                        }
                    }
                    foreach ($paichu['suffix'] as $s) {
                        if (str_ends_with($kaitou, '.' . $s)) {
                            $shibie = '-不要的';
                            break;
                        }
                    }

                    $break = false;
                    foreach ($suffixs2 as $s) {
                        if (str_ends_with($kaitou, '.' . $s)) {
                            $t = explode('.', str_replace('.' . $s, '', $kaitou));
                            $response->push(new Frame(payloadData: json_encode(['domain' => $kaitou, 'ret' => $s, 'body' => count($t) . '级' . $shibie], JSON_UNESCAPED_UNICODE)));
                            $break = true;
                        }
                    }
                    if (!$break) {
                        foreach ($suffixs1 as $s) {
                            if (str_ends_with($kaitou, '.' . $s)) {
                                $t = explode('.', str_replace('.' . $s, '', $kaitou));
                                $response->push(new Frame(payloadData: json_encode(['domain' => $kaitou, 'ret' => $s, 'body' => count($t) . '级' . $shibie], JSON_UNESCAPED_UNICODE)));
                                $break = true;
                            }
                        }
                    }

                    if (!$break) {
                        $response->push(new Frame(payloadData: json_encode(['domain' => $kaitou, 'ret' => '', 'body' => '不能识别' . $shibie], JSON_UNESCAPED_UNICODE)));
                    }
                }
            }
        } else {
            echo "连接已关闭，无法发送消息到 FD {$frame->fd}\n";
        }
    }

    public function onClose($server, int $fd, int $reactorId): void
    {
        // var_dump('websocket-closed');
    }

    public function onOpen($server, $request): void
    {
        $response = (new Response($server))->init($request);
        $response->push(new Frame(payloadData: 'websocket-Opened'));
    }
}
